// SPDX-License-Identifier: GPL-2.0+

#include <linux/string.h>
#include <common.h>
#include <version.h>
#include <spi.h>
#include <spi_flash.h>

#include <asm/sizes.h>
#include <asm/io.h>
#include <asm/reboot.h>

#include "bootparam.h"
#include "loongson_smbios.h"


DECLARE_GLOBAL_DATA_PTR;

#ifndef CONFIG_SF_DEFAULT_SPEED
# define CONFIG_SF_DEFAULT_SPEED	1000000
#endif
#ifndef CONFIG_SF_DEFAULT_MODE
# define CONFIG_SF_DEFAULT_MODE		SPI_MODE_3
#endif
#ifndef CONFIG_SF_DEFAULT_CS
# define CONFIG_SF_DEFAULT_CS		0
#endif
#ifndef CONFIG_SF_DEFAULT_BUS
# define CONFIG_SF_DEFAULT_BUS		0
#endif


#define SPEC_VERS	0x2
#define DMA64_SUPPORT 0x2

#define VRAM_TYPE_SP	0
#define VRAM_TYPE_UMA	1
#define VRAM_SIZE 16

struct efi_memory_map_loongson g_map = { 0 };
struct efi_cpuinfo_loongson g_cpuinfo_loongson = { 0 };
struct system_loongson g_sysitem = { 0 };
struct irq_source_routing_table g_irq_source = { 0 };
struct interface_info g_interface = { 0 };
struct board_devices g_board = { 0 };
struct loongson_special_attribute g_special = { 0 };

struct boot_params gst_param_kernel_bp;

static void tgt_flashinfo(size_t *flashsize)
{
	unsigned int bus = CONFIG_SF_DEFAULT_BUS;
	unsigned int cs = CONFIG_SF_DEFAULT_CS;
	/* In DM mode, defaults speed and mode will be taken from DT */
	unsigned int speed = CONFIG_SF_DEFAULT_SPEED;
	unsigned int mode = CONFIG_SF_DEFAULT_MODE;
	struct spi_flash *flash = NULL;
#ifdef CONFIG_DM_SPI_FLASH
	struct udevice *new, *bus_dev;
	int ret;
#endif

#ifdef CONFIG_DM_SPI_FLASH
	/* Remove the old device, otherwise probe will just be a nop */
	ret = spi_find_bus_and_cs(bus, cs, &bus_dev, &new);
	if (!ret) {
		device_remove(new, DM_REMOVE_NORMAL);
	}

	ret = spi_flash_probe_bus_cs(bus, cs, speed, mode, &new);
	if (ret) {
		printf("Failed to initialize SPI flash at %u:%u (error %d)\n",
		       bus, cs, ret);
		goto done;
	}

	flash = dev_get_uclass_priv(new);
#else
	flash = spi_flash_probe(bus, cs, speed, mode);
#endif

done:
	if (!flash) {
		puts("no flash\n");
		*flashsize = 0;
	} else {
		*flashsize = flash->size;
	}
}

static struct efi_memory_map_loongson *init_memory_map(void)
{
	struct efi_memory_map_loongson *emap = &g_map;
	unsigned long long memorysize = SZ_256M;
	unsigned long long memorysize_high = CONFIG_SYS_SDRAM_SIZE - SZ_256M;
	int i = 0;

#define EMAP_ENTRY(entry, node, type, start, size) \
	emap->map[(entry)].node_id = (node), \
	emap->map[(entry)].mem_type = (type), \
	emap->map[(entry)].mem_start = (start), \
	emap->map[(entry)].mem_size = (size), \
	(entry)++

	EMAP_ENTRY(i, 0, SYSTEM_RAM_LOW,
		0x00200000,
		(memorysize - SZ_16M) >> 20);

	EMAP_ENTRY(i, 0, SYSTEM_RAM_HIGH,
		0x110000000 + (VRAM_SIZE << 20),
		(memorysize_high >> 20) - VRAM_SIZE);

	EMAP_ENTRY(i, 0, SMBIOS_TABLE,
		virt_to_phys((void *)SMBIOS_PHYSICAL_ADDRESS),
		SMBIOS_SIZE_LIMIT >> 20);
	//UMA_VIDEO_RAM ?
	EMAP_ENTRY(i, 0, SYSTEM_RAM_RESERVED,
		0x110000000,
		VRAM_SIZE);

	emap->vers = 1;
	emap->nr_map = i;
	emap->mem_freq = gd->mem_clk;

	return emap;
}

static struct efi_cpuinfo_loongson *init_cpu_info(void)
{
	struct efi_cpuinfo_loongson *c = &g_cpuinfo_loongson;

	c->processor_id = 0x6100;
	c->cputype  = Legacy_2K;

	c->cpu_clock_freq = gd->cpu_clk;

	c->total_node = 1;
	c->nr_cpus = 2;

	c->cpu_startup_core_id = 0;
	c->reserved_cores_mask = 0;

	return c;
}

static struct system_loongson *init_system_loongson(void)
{
	struct system_loongson *s = &g_sysitem;

	s->ccnuma_smp = 1;
	s->vers = 2;

	return s;
}

enum loongson_irq_source_enum
{
	HT,
	I8259,
	UNKNOWN
};

static struct irq_source_routing_table *init_irq_source(void)
{
	struct irq_source_routing_table *irq_info = &g_irq_source;

	irq_info->PIC_type = HT;

	irq_info->ht_int_bit = 1 << 24;
	irq_info->ht_enable = 0x0000d17b;

	irq_info->node_id = 0;

	irq_info->pci_io_start_addr = 0x18000000;
	irq_info->pci_mem_start_addr = 0x40000000ul;
	irq_info->pci_mem_end_addr = 0x7ffffffful;
	irq_info->dma_mask_bits = 64;

	return irq_info;
}

static struct interface_info *init_interface_info(void)
{
	struct interface_info *inter = &g_interface;
	size_t flashsize;

	tgt_flashinfo(&flashsize);

	inter->vers = SPEC_VERS;
	inter->size = flashsize / 1024;
	inter->flag = 1;

	strcpy(inter->description, U_BOOT_VERSION);

	return inter;
}

static struct board_devices *board_devices_info(void)
{
	struct board_devices *bd = &g_board;

	strcpy(bd->name, CONFIG_LOONGSON_BOARD_NAME);

	bd->num_resources = 10;

	return bd;
}

static struct loongson_special_attribute *init_special_info(void)
{
	struct loongson_special_attribute  *special = &g_special;

	strcpy(special->special_name, U_BOOT_DATE);

	special->resource[0].flags = DMA64_SUPPORT;
	special->resource[0].start = 0;
	special->resource[0].end = VRAM_SIZE;
	strcpy(special->resource[0].name, "SPMODULE");

	return special;
}

static void init_loongson_params(struct loongson_params *lp)
{
	lp->memory_offset = (unsigned long long)((void *)init_memory_map() - (void *)lp);
	lp->cpu_offset = (unsigned long long)((void *)init_cpu_info() - (void *)lp);
	lp->system_offset = (unsigned long long)((void *)init_system_loongson() - (void *)lp);
	lp->irq_offset = (unsigned long long)((void *)init_irq_source() - (void *)lp);
	lp->interface_offset = (unsigned long long)((void *)init_interface_info() - (void *)lp);
	lp->boarddev_table_offset = (unsigned long long)((void *)board_devices_info() - (void *)lp);
	lp->special_offset = (unsigned long long)((void *)init_special_info() - (void *)lp);
}

static void init_smbios(struct smbios_tables *smbios)
{
	smbios->vers = 0;
	smbios->vga_bios = 0;
}

static void init_efi(struct efi_loongson *efi)
{
	struct smbios_tables *smbios = &(efi->smbios);
	struct loongson_params *lp = &(smbios->lp);

	init_smbios(smbios);
	init_loongson_params(lp);
}

static void init_reset_system(struct efi_reset_system_t *reset)
{
	reset->Shutdown = NULL;
	reset->ResetWarm = &_machine_restart;
}

static int init_boot_param(struct boot_params *bp)
{
	struct efi_loongson *efi = &(bp->efi);
	struct efi_reset_system_t *reset = &(bp->reset_system);

	init_efi(efi);
	init_reset_system(reset);

	return 0;
}

void *build_boot_param(void)
{
	struct boot_params *bp = &gst_param_kernel_bp;
	init_boot_param(bp);
	return bp;
}
